package cxm.proj.xtalk.smack;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;

import cxm.lib.util.MyLog;
import cxm.proj.xtalk.smack.Call.CallNotify;

public class CallManager implements PacketListener, CallNotify {
	public static final String STRING_REQUEST = "V1.0/XTALK_REQUEST_CONFERENCE";
	public static final String STRING_ACCEPT = "V1.0/XTALK_ACCEPT_CONFERENCE";
	public static final String STRING_DISACCEPT = "V1.0/XTALK_DISACCEPT_CONFERENCE";
	public static final String STRING_STOP_REQUEST = "V1.0/XTALK_STOP_CONFERENCE";
	public static final String STRING_ERROR = "V1.0/ERROR";
	public static final String STRING_SPLITTER = "@";
	public static final int dataLen = 160 * 2;
	public static final int port = 23487;
	
	public static final int ERRNO_TRANSFER_UNCAN = -1;
	public static final int ERRNO_REPLY_UNCAN = -2;
	public static final int ERRNO_RECEIVE_ERROR = -3;
	public static final int ERRNO_START_UNCAN = -4;
	
	public interface CallManagerNotify {
		public void onCall(Call call);
	}
	
	public class CallException extends Exception {
		private static final long serialVersionUID = -5061004456892071590L;

		public CallException(String msg) {
			super("CallException: " + msg);
		}
	}

	private static CallManager instance = null;
	private CallManagerNotify callManagerNotify = null;
	private Call currentCall = null;

	private CallManager() {
		
	}
	
	public static CallManager getInstance() {
		if (null == instance)
			instance = new CallManager();
		
		return instance;
	}
	
	public Call getCurrentCall() {
		if (null == this.currentCall)
			throw new IllegalStateException("null");
		return this.currentCall;
	}
	
	// start listen call received
	public void startListen(CallManagerNotify notify) throws CallException {
		if (null != this.callManagerNotify)
			throw new CallException("Already listen");

		// register pakcage listener
		XMPPConnection xmpp = ConnectManager.getInstance().getConnector().getConnection();
		xmpp.addPacketListener(this, new PacketFilter() {
			@Override
			public boolean accept(Packet packet) {
				return true;
			}
		});
		this.callManagerNotify = notify;
	}
	
	public void stopListen() {
		if (null == this.callManagerNotify)
			return;
		
		XMPPConnection xmpp = ConnectManager.getInstance().getConnector().getConnection();
		xmpp.removePacketListener(this);
		this.callManagerNotify = null;
	}
	
	// hold on a call, with sending an id
	// caller will call this method to callee
	public void callTo(String name) throws CallException {
		// check all exist
		if (null != this.currentCall)
			throw new CallException("Already call in");

		// construct call
		Call call = new UdpCall(UserManager.getInstance().getUser().getName(),
				name, port, dataLen);
		MyLog.Log(MyLog.Level.Info, String.format("start conference from %s to %s",
				call.getCaller(), call.getCallee()));
		
		// send request package to connection
		Chat chat = call.getChat();
		try {
			chat.sendMessage(STRING_REQUEST + STRING_SPLITTER + call.getSelfIdentical());
		} catch (Exception e) {
			MyLog.Log(MyLog.Level.Error, "Cannot send message: " + e);
			throw new CallException(e.toString());
		}
		// callee receive onAccept notify
		this.addCall(call);
	}
	
	// callee will call this method to response with caller
	public void acceptCall(boolean isAccept) throws CallException {
		// check all exist
		if (null == this.currentCall)
			throw new CallException("No call in");

		// callee send accept packet to caller
		Chat chat = this.currentCall.getChat();
		try {
			if (isAccept) {
				chat.sendMessage(STRING_ACCEPT + STRING_SPLITTER + this.currentCall.getSelfIdentical());
				// fire notify
				this.currentCall.fireOnConnect();
			}
			else {
				chat.sendMessage(STRING_DISACCEPT);
				this.currentCall.finish();
				this.removeCall();
			}
		} catch (Exception e) {
			MyLog.Log(MyLog.Level.Error, "Cannot send package: " + e);
			throw new CallException(e.toString());
		}
	}
	
	// stop a call
	public void hangup() {
		if (null == this.currentCall) {
			MyLog.Log(MyLog.Level.Warning, "No current chat available");
			return;
		}
		// caller send hangup package to callee
		Chat chat = this.currentCall.getChat();
		try {
			chat.sendMessage(STRING_STOP_REQUEST);
		} catch (Exception e) {
			MyLog.Log(MyLog.Level.Error, "Cannot send message: " + e);
		}

		// then fire call notify
		this.currentCall.fireOnCutOff();
		this.currentCall.finish();
		
		MyLog.Log(MyLog.Level.Info, String.format("Stop conference from %s to %s",
				this.currentCall.getCaller(), this.currentCall.getCallee()));
		this.removeCall();
	}
	
	/*
	public void setCallNotify(CallManagerNotify notify) {
		if (null == notify)
			this.callManagerNotify = null;
		else {
			if (null != this.callManagerNotify)
				throw new IllegalStateException("CallNotify exists");
			this.callManagerNotify = notify;
		}
	}
	
	public void removeCallNotify(CallManagerNotify notify) {
		if (notify == this.callManagerNotify)
			this.callManagerNotify = null;
	}
	*/

	@Override
	public void processPacket(Packet pkg) {
		// MyLog.Log(MyLog.Level.Debug, "Receive package: " + pkg.toXML());

		if (!(pkg instanceof Message) || null == this.callManagerNotify) {
			return;
		}
		if (null != this.currentCall) {
			MyLog.Log(MyLog.Level.Info, "Already in call, ignore request");
			return;
		}

		// fire notify if incoming call access
		String body = ((Message)pkg).getBody();
		if (body.contains(STRING_REQUEST)) {
			String callerName = pkg.getFrom();
			String callerId = splitIdentical(body);

			// create a call
			Call call = new UdpCall(callerName + "@" +
					ConnectManager.getInstance().getServiceName(),
					UserManager.getInstance().getUser().getName(),
					port, dataLen);
			call.setTargetIdentical(callerId);

			this.addCall(call);
			// notify with id
			this.callManagerNotify.onCall(call);
		}
	}
	
	public static String splitIdentical(String value) {
		// split string
		String result = value.split(STRING_SPLITTER)[1];
		
		return result;
	}

	private void addCall(Call call) {
		// assume no call exist
		call.setCallNotify(this);
		this.currentCall = call;
	}
	
	private void removeCall() {
		this.currentCall.removeCallNotify(this);
		this.currentCall = null;
	}

	@Override
	public void onAccept(boolean isAccept) {
		if (!isAccept) {
			MyLog.Log(MyLog.Level.Info, "Disaccept call, remove from CallManager");
			this.removeCall();
		}
	}

	@Override
	public void onConnect() {
		return; // ignore
	}

	@Override
	public void onCutOff() {
		MyLog.Log(MyLog.Level.Info, "Disaccept call, remove from CallManager");
		this.removeCall();
	}

	@Override
	public void onCallNotifyDataReceived(byte[] data) {
		return; // ignore
	}

	@Override
	public void onError(int errno, Object param) {
		return; // ignore
	}
}
